use std::ops::Deref;
use std::rc::Rc;

use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::ProtocolObject;
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_foundation::{CGSize, MainThreadMarker, NSObject};
use objc2_ui_kit::{UIResponder, UIView, UIViewController, UIViewControllerTransitionCoordinator, UIWindow};

use super::{Factor, Window, Vars, WinRef, XLoopView};
use crate::win::ViewVars;

declare_class!(
	#[derive(Debug)]
	pub struct XLoopWindow;
	unsafe impl ClassType for XLoopWindow {
		#[inherits(UIView, UIResponder, NSObject)]
		type Super = UIWindow;
		type Mutability = mutability::MainThreadOnly;
		const NAME: &'static str = "XLoopWindow";
	}
	impl DeclaredClass for XLoopWindow {
		type Ivars = Rc<Vars>;
	}
	unsafe impl XLoopWindow {
		#[method(updateProxy:)]
		fn update_proxy(&self,value:i64){ self.update_proxy_(value) }
	}
);
impl XLoopWindow {
	pub fn new(_key: impl AsRef<str>, rep: impl types::WinRep<Window> + 'static) -> Option<Id<XLoopWindow>> {
		let rep = Rc::new(rep);
		autoreleasepool(|_| {
			let mtm = MainThreadMarker::new().unwrap();

			let vars = Rc::new(Vars {
				rep: rep.clone(),
				refresh: true.into(),
				window: Default::default(),
				controller: Default::default(),
				view: Default::default(),
				touches: Default::default(),
			});

			let controller = mtm.alloc().set_ivars(vars.clone());
			let controller: Id<XLoopController> = unsafe { msg_send_id![super(controller), init] };
			*vars.controller.borrow_mut() = Some(controller.retain());

			let scenes = unsafe { crate::app::AppHandle::singleton().0.connectedScenes() };
			let scene = scenes.iter().next()?;

			let window: Id<XLoopWindow> = unsafe {
				msg_send_id![
					super(mtm.alloc().set_ivars(vars.clone())),
					initWithWindowScene: scene,
				]
			};
			*vars.window.borrow_mut() = Some(window.retain());

			let view = mtm.alloc().set_ivars(ViewVars::new(vars.clone()));
			let view: Id<XLoopView> = unsafe { msg_send_id![super(view), init] };
			*vars.view.borrow_mut() = Some(view.retain());

			// unsafe { view.setPaused(true) };
			// unsafe { view.setEnableSetNeedsDisplay(true) };

			controller.setView(Some(&view));

			window.setRootViewController(Some(&controller));

			rep.on_create(WinRef(&window, &view));

			Some(window)
		})
	}
	fn update_proxy_(&self, value: i64) {
		if let Some(view) = self.ivars().view.borrow().deref() {
			self.ivars().rep.on_proxy(WinRef(&self, &view), value);
		}
	}
}
declare_class!(
	#[derive(Debug)]
	pub	struct XLoopController;
	unsafe impl ClassType for XLoopController {
		#[inherits(UIResponder, NSObject)]
		type Super = UIViewController;
		type Mutability = mutability::MainThreadOnly;
		const NAME: &'static str = "XLoopController";
	}
	impl DeclaredClass for XLoopController {
		type Ivars = Rc<Vars>;
	}
	unsafe impl XLoopController {
		#[method(viewWillTransitionToSize:withTransitionCoordinator:)]
		unsafe fn view_will_transition_to_size_with_transition_coordinator(
			&self,
			size: CGSize,
			coordinator: &ProtocolObject<dyn UIViewControllerTransitionCoordinator>,
		){
			unsafe { msg_send![super(self), viewWillTransitionToSize:size withTransitionCoordinator:coordinator] }
			self.view_will_transition_to_size(size);
		}
	}
);
impl XLoopController {
	fn view_will_transition_to_size(&self, size: CGSize) {
		let vars = self.ivars();
		let (window, view) = (vars.window.borrow(), vars.view.borrow());
		if let (Some(window), Some(view)) = (window.as_ref(), view.as_ref()) {
			let factor = Factor::from(view.deref());
			let win = WinRef(&window, &view);
			self.ivars()
				.rep
				.on_resize(win, (factor.to_u32(size.width), factor.to_u32(size.height)));
			use types::WinRef as _;
			win.fresh("view_will_transition_to_size");
		}
	}
}
